#!/bin/bash
# vim:ts=4:sw=4:expandtab

############################################################
# NAME
#     dfw - dox's tool for make tcp forwarding to remote host
#
# SYNOPSIS
#     dfw options... host
#
#     debug=on dfw ...
#
# AUTHORS
#     neiku project <ku7d@qq.com>
#
# SEE ALSO
#     doxrc_hosts
#
# VERSION
#     2016/04/08: 支持基于dsshL的多重主机穿透(本地端口映射)
#                 支持穿透每层主机后等待50ms，以保证可以继续
#     2016/04/09: 支持基于RSA验证方式的主机穿透
#     2016/04/10: 支持隐藏RSA验证过程中yes/no人机交互过程
#                 支持verbose模式将穿透进度重定向到stderr设备
#     2016/05/08: 支持同步等待端口映射完成(定时检测)
#                 (提高端口映射成功率)
#     2016/05/10: 支持通过环境变量传递password，提高安全
#     2019/02/19: 默认使用home目录下的配置，提高灵活性
#                 eval赋值计算中，值部分补充单引号，增强可用性
#
############################################################

# help
function help()
{
    echo "Usage: drun [options...] host"
    echo "Options:"
    echo "  -h, --help          Print this message and exit."
    echo "  -v, --verbose       Make the operation more talkative."
    echo ""
    echo "Report bugs to <ku7d@qq.com>"
}

# dfw [options...] host
cmdline_host=""
cmdline_verbose="no"

# parse cmdline
dlog debug "origin-args:[$@]"
temp=$(getopt -o "hv" --long "help,verbose" -n "dfw" -- "$@")
if [ $? != 0 ] ; then
    echo "`help`" >&2
    exit 1
fi
eval set -- "$temp"
dlog debug "parsed-args:[$temp]"
while true
do
    case "$1" in
        -h|--help) echo "`help`" >&2; exit 0;;
        -v|--verbose) cmdline_verbose="yes"; shift ;;
        --) shift ; break ;;
        *)  echo "parse options error!" >&2 ; exit 1 ;;
    esac
done
cmdline_host="$1"
if [ -z "$cmdline_host" ] ; then
    dlog error "check host fail, host:[$cmdline_host]"
    exit 1
fi
dlog debug "host:[$cmdline_host]"

# load host config
hosts=$HOME/doxrc_hosts
hname=; htype=; haddr=; hport=; huser=; hpass=; hproxys=;
eval `grep -v -E '^#|^$' $hosts 2>/dev/null                \
      | grep $cmdline_host 2>/dev/null                     \
      | head -n1                                           \
      | awk '{printf "hname='\''%s'\''; htype='\''%s'\'';  \
                      haddr='\''%s'\''; hport='\''%s'\'';  \
                      huser='\''%s'\''; hpass='\''%s'\'';  \
                      hproxys='\''%s'\'';"                 \
                     , $1, $2                              \
                     , $3, $4                              \
                     , $5, $6                              \
                     , $7}'`

if [    -z "$hname" -o -z "$htype" \
     -o -z "$haddr" -o -z "$hport" \
     -o -z "$huser" -o -z "$hpass" ] ; then
    dlog error "check host config fail,"         \
               "hname:[$hname], htype:[$htype]," \
               "haddr:[$haddr], hport:[$hport]," \
               "huser:[$huser], hpass:[$hpass]"
    exit 1
fi
dlog debug "load host config succ,"          \
           "hname:[$hname], htype:[$htype]," \
           "haddr:[$haddr], hport:[$hport]," \
           "huser:[$huser], hpass:[$hpass]," \
           "hproxys:[$hproxys]"

# no tcp forwarding for noproxy-host
if [ -z "$hproxys" -o "$hproxys" = "noproxy" ] ; then
    echo $haddr:$hport
    if [ "$cmdline_verbose" = "yes" ] ; then
        echo "tcp forwarding for $cmdline_host ok," \
             "listen at $haddr:$hport" >&2
    fi
    exit 0
fi

prehost=""
allhost="$hproxys:$cmdline_host"
laddr=""
lport=""
dlog debug "making tcp forwarding, host:[$cmdline_host]," \
           "proxys:[$hproxys], allhost:[$allhost]"
echo "$allhost" | tr ':' '\n' | while read host
do
    if [ -z "$laddr" ] ; then
        listen="`dfw $host 2>/dev/null`"
        if [ $? != 0 ] ; then
            dlog error "dfw fail, host:[$host]"
            break
        fi
        laddr="`echo $listen | tail -n1 | cut -d: -f1`"
        lport="`echo $listen | tail -n1 | cut -d: -f2`"
        dlog debug "dfw succ, host:[$host], listen:[$listen]," \
                   "addr:[$laddr], port:[$lport]"
        echo $laddr:$lport
        if [ "$cmdline_verbose" = "yes" ] ; then
            echo "tcp forwarding for $host ok," \
                 "listen at $laddr:$lport" >&2
        fi

        prehost=$host
        continue
    fi

    # load host config for proxy
    hpname=; hptype=; hpaddr=; hpport=; hpuser=; hppass=;
    eval `grep -v -E '^#|^$' $hosts 2>/dev/null      \
          | grep $prehost 2>/dev/null                \
          | head -n1                                 \
          | awk '{printf "hpname=%s; hptype=%s;      \
                          hpaddr=%s; hpport=%s;      \
                          hpuser=%s; hppass=%s"      \
                         , $1, $2                    \
                         , $3, $4                    \
                         , $5, $6}'`
    dlog debug "load prehost '$prehost' config succ,"\
               "hpname:[$hpname], hptype:[$hptype]," \
               "hpaddr:[$hpaddr], hpport:[$hpport]," \
               "hpuser:[$hpuser], hppass:[$hppass]"

    # load host config for tcp-forwarding
    hfname=; hftype=; hfaddr=; hfport=; hfuser=; hfpass=;
    eval `grep -v -E '^#|^$' $hosts 2>/dev/null      \
          | grep $host 2>/dev/null                   \
          | head -n1                                 \
          | awk '{printf "hfname=%s; hftype=%s;      \
                          hfaddr=%s; hfport=%s;      \
                          hfuser=%s; hfpass=%s"      \
                         , $1, $2                    \
                         , $3, $4                    \
                         , $5, $6}'`
    dlog debug "load host '$host' config succ,"      \
               "hfname:[$hfname], hftype:[$hftype]," \
               "hfaddr:[$hfaddr], hfport:[$hfport]," \
               "hfuser:[$hfuser], hfpass:[$hfpass]"

    # random port for tcp-forwarding
    port=$(($(ss -at4n | awk '{print $4}' \
              | cut -d: -f2 | sort -nu | tail -n1) + 1))
    if [ "$port" -lt 1024 ] ; then
        port=$((port + 50000))
    fi

    # make tcp-forwarding
    case "$hptype" in
        rsa)
            ssh -o PasswordAuthentication=no -o StrictHostKeyChecking=no \
                -L $port:$hfaddr:$hfport $hpuser@$laddr \
                -p $lport -q -i $hppass -f "sleep 10"
            if [ $? != 0 ] ; then
                dlog error "make tcp forwarding fail(rsa proxy)," \
                           "proxy-host:[$prehost], remote-host:[$host]"
                exit 1
            fi
            ;;
        passwd)
            DOX_DSSHL_PROXYPASSWORD="$hppass" \
            dsshL $hpuser "" $laddr $lport \
                  $hfaddr $hfport \
                  $port "sleep 10" >/dev/null 2>&1
            if [ $? != 0 ] ; then
                dlog error "make tcp forwarding fail(passwd proxy)," \
                           "proxy-host:[$prehost], remote-host:[$host]"
                exit 1
            fi
            ;;
        *)
            dlog error "host type unsupported, type:[$hptype]," \
                       "supported type:[rsa, passwd]"
            ;;
    esac
    laddr=127.0.0.1
    lport=$port
    echo $laddr:$lport
    if [ "$cmdline_verbose" = "yes" ] ; then
        echo "tcp forwarding for $host ok," \
             "listen at $laddr:$lport" >&2
    fi

    # current host become next proxy host
    prehost=$host

    # wait with progress until ok
    iswait="false"
    while true
    do
        ok="$(ss -ltn -4 | grep -w $laddr:$lport | wc -l)"
        if [ "$ok" = "1" ] ; then
            break
        fi

        iswait="true"
        if [ "$cmdline_verbose" = "yes" ] ; then
            echo -n "#" >&2
        fi
        usleep 50000
    done
    if [ "$iswait" = "true" -a "$cmdline_verbose" = "yes" ] ; then
        echo >&2
    fi

done || { exit 1; }
